零件都準備好就可以組裝起來了!
前幾天分別完成了redis
, error
, log
的封裝, 接下來就是依照API的input/output分別組合出各個API的內容。
API可以善用defer
來log API的進出資訊以及執行時間, 記錄下總執行時間可以分析出API處理時長是否符合期望值, ex: 一句redis語法預期會是1ms, 打一個外部gRPC API要在50ms以內得到回應...等等, 紀錄時間可以做為服務調校的依據, 當API流量超過預期時, 這些時間就會是API優化的參考值。
服務log下來的各項資訊及error可以透過ELK收集起來, 收集之後還可以依照訊息的等級決定是否轉發到email或通訊軟體, 目前通訊轉體都有開放API提供介接轉發訊息。
把處理行為都封裝好之後, error 發生時也可以透過log清楚地知道是哪一個funcion發生問題, 不僅每支function行數變簡潔, 對於問題發生時釐清查找範圍也很有幫助!
組合出來的結果大致如下:
package main
import (
"context"
"errors"
"regexp"
"strconv"
"time"
coconutError "github.com/evelynocean/coconut/lib/error"
coconut_model "github.com/evelynocean/coconut/model"
coconut "github.com/evelynocean/coconut/pb"
coconut_redis "github.com/evelynocean/coconut/redis"
)
// Ping 提供服務運行確認
func (s *server) Ping(ctx context.Context, in *coconut.PingRequest) (r *coconut.Pong, err error) {
r = &coconut.Pong{
Pong: "pong",
}
return r, err
}
// 更新統計點數
func (s *server) UpdatePoints(ctx context.Context, in *coconut.PointsRequest) (r *coconut.RetResultStatus, err error) {
start := time.Now()
defer func() {
Logger.WithFields(map[string]interface{}{
"input": in,
"execute_time": time.Since(start).Seconds(),
"response": r,
}).Debugf("UpdatePoints")
// HandlerPanicRecover(&err)
}()
// TODO: input data check
if in.Level_1 == "" || in.Level_2 == "" || in.Level_3 == "" {
return nil, coconutError.ParseError(coconutError.ErrServer, errors.New("invalid parameter"))
}
r = &coconut.RetResultStatus{}
sets := &coconut_redis.KeySet{
Level1: in.Level_1,
Level2: in.Level_2,
Level3: in.Level_3,
UserName: in.UserName,
}
keys := coconut_redis.GetPointKey(sets)
limitSettings, err := coconut_model.GetLimit(s.ScyllaSession)
for idx, v := range keys {
limit := limitSettings[strconv.Itoa(idx)]
err = coconut_redis.PointSet(s.RedisClient, v, int(in.Point), time.Duration(30)*time.Second, limit)
if err != nil {
Logger.WithFields(map[string]interface{}{
"key": v,
"point": in.Point,
"limit": limit,
"time": time.Now().UnixNano(),
"err:": err.Error(),
}).Errorf("redis.PointSet")
return nil, coconutError.ParseError(coconutError.ErrRedis, err)
}
}
r = &coconut.RetResultStatus{
Success: true,
}
return r, nil
}
// 查詢統計點數
func (s *server) GetPoints(ctx context.Context, in *coconut.GetPointsRequest) (r *coconut.RetPoints, err error) {
start := time.Now()
var data []*coconut.PointInfo
defer func() {
Logger.WithFields(map[string]interface{}{
"input": in,
"execute_time": time.Since(start).Seconds(),
"response": r,
}).Debugf("GetPoints")
// HandlerPanicRecover(&err)
}()
if in.Level_1 == "" || in.Level_2 == "" || in.Level_3 == "" {
return nil, coconutError.ParseError(coconutError.ErrServer, errors.New("invalid parameter"))
}
// 解析出各個層級
reg := regexp.MustCompile(`^LEVEL.*:(\w+)$`)
sets := &coconut_redis.KeySet{
Level1: in.Level_1,
Level2: in.Level_2,
Level3: in.Level_3,
}
keys := coconut_redis.GetPointKey(sets)
for _, v := range keys {
resultPoint, err := coconut_redis.PointGet(s.RedisClient, v)
if err != nil {
Logger.WithFields(map[string]interface{}{
"key": v,
"time": time.Now().UnixNano(),
"err:": err.Error(),
}).Errorf("redis.PointGet")
return nil, coconutError.ParseError(coconutError.ErrRedis, err)
}
if resultPoint > 0 {
matchSlice := reg.FindStringSubmatch(v)
d := &coconut.PointInfo{
Name: matchSlice[1],
Points: int32(resultPoint),
}
data = append(data, d)
}
}
r = &coconut.RetPoints{
Data: data,
}
return r, nil
}